package top.tony.base.dao;

import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.transform.Transformers;
import org.springframework.beans.factory.annotation.Autowired;
import top.tony.base.constants.Constants;
import top.tony.base.pagenation.BaseQueryObject;
import top.tony.base.pagenation.PageModel;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.math.BigInteger;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Created by Tony_Du on 2015/12/3 0003.
 */
public abstract class BaseDaoImpl<Entity> implements IBaseDao<Entity> {

    private Class<Entity> clz;

    public BaseDaoImpl() {
        Class<? extends BaseDaoImpl> realClz = this.getClass();
        ParameterizedType parameterizedType = (ParameterizedType) realClz.getGenericSuperclass();
        Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
        clz = (Class) actualTypeArguments[0];
    }

    @Autowired
    private SessionFactory sessionFactory;

    protected Session getSession() {
        return sessionFactory.getCurrentSession();
    }

    public Serializable save(Entity entity) {
        return getSession().save(entity);
    }

    public int delete(Serializable id) {
        return getSession().createQuery("DELETE FROM " + clz.getName() + " WHERE id=:id")
                .setParameter("id", id)
                .executeUpdate();
    }

    public void update(Entity entity) {
        getSession().update(entity);
    }

    public Entity get(Serializable id) {
        return (Entity) getSession().get(clz, id);
    }

    public List<Entity> getAll() {
        return getSession().createQuery("FROM " + clz.getName())
                .list();
    }

    public PageModel<Entity> pageQuery(BaseQueryObject queryObejct) {
        return basePageQuery(clz, queryObejct, false);
    }

    public <VO> PageModel<VO> voPageQuery(Class<VO> voClass, BaseQueryObject queryObejct) {
        return basePageQuery(voClass, queryObejct, true);
    }

    private <T> PageModel<T> basePageQuery(Class<T> retItemClass, BaseQueryObject queryObejct, boolean isVo) {
        int pageNo = queryObejct.getPageNo();
        int pageSize = queryObejct.getPageSize();
        pageNo = pageNo > 0 ? pageNo : 1;
        pageSize = pageSize > 0 ? pageSize : Constants.DEFAULT_PAGE_SIZE;

        Session session = getSession();
        String selectSql = queryObejct.getSelectSql().toString();
        String countSql = queryObejct.getCountSql().toString();
        Set<Map.Entry<String, Object>> param = queryObejct.getParam().entrySet();

        Query countQuery = isVo ? session.createSQLQuery(countSql) : session.createQuery(countSql);
        for (Map.Entry<String, Object> entry : param) {
            countQuery.setParameter(entry.getKey(), entry.getValue());
        }

        Object countRet = countQuery.uniqueResult();
        Long count = isVo ? ((BigInteger) countRet).longValue() : (Long) countRet;

        List<T> data = Collections.EMPTY_LIST;

        if (count > 0) {
            int totalPage = (int) ((count + pageSize - 1) / pageSize);
            pageNo = pageNo < totalPage ? pageNo : totalPage;
            Query query = isVo ? session.createSQLQuery(selectSql) : session.createQuery(selectSql);

            query.setFirstResult((pageNo - 1) * pageSize)
                    .setMaxResults(pageSize);

            for (Map.Entry<String, Object> entry : param) {
                query.setParameter(entry.getKey(), entry.getValue());
            }

            if (isVo) {
                query.setResultTransformer(Transformers.aliasToBean(retItemClass));
            }

            data = query
                    .list();
        }

        return new PageModel<T>(pageNo, pageSize, count, data);
    }

    public <VO> PageModel<VO> createPageQuery(int pageNo, int pageSize, Map<String, Object> param, String selectSql, String countSql, Class<VO> voClass) {
        pageNo = pageNo > 0 ? pageNo : 1;
        pageSize = pageSize > 0 ? pageSize : Constants.DEFAULT_PAGE_SIZE;

        Session session = getSession();

        Set<Map.Entry<String, Object>> paramMapEntrySet = param.entrySet();

        Query countQuery = session.createSQLQuery(countSql);
        for (Map.Entry<String, Object> entry : paramMapEntrySet) {
            countQuery.setParameter(entry.getKey(), entry.getValue());
        }

        Long count = ((BigInteger) countQuery.uniqueResult()).longValue();

        List<VO> data = Collections.EMPTY_LIST;

        if (count > 0) {
            int totalPage = (int) ((count + pageSize - 1) / pageSize);
            pageNo = pageNo < totalPage ? pageNo : totalPage;
            Query query = session.createSQLQuery(selectSql);

            query.setFirstResult((pageNo - 1) * pageSize)
                    .setMaxResults(pageSize);

            for (Map.Entry<String, Object> entry : paramMapEntrySet) {
                query.setParameter(entry.getKey(), entry.getValue());
            }

            query.setResultTransformer(Transformers.aliasToBean(voClass));

            data = query
                    .list();
        }

        return new PageModel<VO>(pageNo, pageSize, count, data);
    }
}
